#include "svdpi.h"
#include "common.h"
#include <queue>
#include <vector>
#include <string>

class CRAT
{
public:
    int gc_num, prf_size;
    std::vector<int> rat;
    std::vector<bool> rat_valid;
    std::queue<std::vector<bool>> gc_buffer;
    CRAT(int gc_num, int prf_size) : gc_num(gc_num), prf_size(prf_size)
    {
        rat.resize(prf_size);
        rat_valid.resize(prf_size);
        clear();
    }
    ~CRAT() = default;
    int read(int arf_idx)
    {
        for (size_t i = 0; i < prf_size; i++)
        {
            if (rat_valid[i] && rat[i] == arf_idx)
            {
                return i;
            }
        }
        LOG(COLOR("CRAT read Error : arf %d not found ! at %lu", RED), arf_idx, Verilated::time());
        Verilated::gotFinish(true);
        return -1;
    }
    void write(int arf_idx, int prf_idx)
    {
        if (rat_valid[prf_idx])
        {
            LOG(COLOR("CRAT write Error : prf %d not free ! at %lu", RED), prf_idx, Verilated::time());
            Verilated::gotFinish(true);
        }
        if (prf_idx == 0)
        {
            LOG(COLOR("CRAT Write Error : try free prf %d at %lu", RED), prf_idx, Verilated::time());
            Verilated::gotFinish(true);
        }
        int old_prf_idx = read(arf_idx);
        rat_valid[old_prf_idx] = false;
        rat_valid[prf_idx] = true;
        rat[prf_idx] = arf_idx;
    }
    void save_gc()
    {
        if (gc_buffer.size() >= gc_num)
        {
            LOG(COLOR("CRAT Error : gc buffer full", RED));
            Verilated::gotFinish(true);
            return;
        }
        gc_buffer.push(rat_valid);
        // LOG(COLOR("CRAT GC NUM : %lu", GREEN), gc_buffer.size());
    }
    void commit_gc()
    {
        if (gc_buffer.empty())
        {
            LOG(COLOR("CRAT Error : gc buffer empty at commit gc", RED));
            Verilated::gotFinish(true);
            return;
        }
        gc_buffer.pop();
    }
    void restore_gc()
    {
        if (gc_buffer.empty())
        {
            LOG(COLOR("CRAT Error : gc buffer empty at restore gc", RED));
            Verilated::gotFinish(true);
            return;
        }
        rat_valid = gc_buffer.front();
        while (!gc_buffer.empty())
        {
            gc_buffer.pop();
        }
    }
    void clear()
    {
        while (!gc_buffer.empty())
        {
            gc_buffer.pop();
        }

        for (size_t i = 0; i < prf_size; i++)
        {
            if (i < 32)
            {
                rat_valid[i] = true;
                rat[i] = i;
            }
            else
            {
                rat_valid[i] = false;
                rat[i] = 0;
            }
        }
    }
};
CRAT *debug_crat;
static std::vector<CRAT *> crats;
static std::vector<std::string> crat_names;
static int crat_id = 0;
#ifdef __cplusplus
extern "C"
{
#endif

    int crat_create(int id, int gc_num, int prf_size, const char *name)
    {
        if (id == 0)
        {
            crats.push_back(new CRAT(gc_num, prf_size));
            crat_names.push_back(std::string(name));
            crat_id++;
            LOG("CRAT[%d] %s created", crat_id, name);
            debug_crat = crats[0];
            return crat_id;
        }
        else
        {
            auto crat = crats[id - 1];
            crat->clear();
            return id;
        }
    }
    int crat_ctrl(int id, svBit save_gc, svBit commit_gc, svBit restore_gc)
    {
        if (id == 0)
            return id;
        auto crat = crats[id - 1];
        if (save_gc)
        {
            // LOG("CRAT save_gc at %lu", Verilated::time());
            crat->save_gc();
        }

        if (commit_gc)
        {
            if (restore_gc)
            {
                // LOG("CRAT commit and restore_gc at %lu", Verilated::time());
                crat->restore_gc();
            }
            else
            {
                // LOG("CRAT commit_gc at %lu", Verilated::time());
                crat->commit_gc();
            }
        }
        return id;
    }
    int crat_read(int id, int arf_idx, int prf_idx)
    {
        if (id == 0)
            return id;
        auto crat = crats[id - 1];
        int ref_prf_idx = crat->read(arf_idx);
        if (prf_idx != ref_prf_idx)
        {
            LOG(COLOR("CRAT Read Error : arf %d in DUT %d not equal REF %d at %lu", RED), arf_idx, prf_idx, ref_prf_idx, Verilated::time());
        }
        // LOG("CRAT Read %d -> %d", arf_idx, prf_idx);
        return id;
    }
    int crat_write(int id, int arf_idx, int prf_idx)
    {
        if (id == 0)
            return id;
        auto crat = crats[id - 1];
        crat->write(arf_idx, prf_idx);
        // LOG("CRAT Write %d -> %d", arf_idx, prf_idx);
        return id;
    }

#ifdef __cplusplus
}
#endif